#include <G4Box.hh>
#include <G4Element.hh>
#include <G4LogicalBorderSurface.hh>
#include <G4LogicalSkinSurface.hh>
#include <G4LogicalVolume.hh>
#include <G4Material.hh>
#include <G4NistManager.hh>
#include <G4OpticalSurface.hh>
#include <G4PVPlacement.hh>
#include <G4SDManager.hh>
#include <G4ThreeVector.hh>
#include <G4Tubs.hh>
#include <G4VPhysicalVolume.hh>

#include "DetectorConstruction.hh"
#include "PMTDetector.hh"

DetectorConstruction::DetectorConstruction() {
	;
}

DetectorConstruction::~DetectorConstruction() {
	;
}

G4VPhysicalVolume* DetectorConstruction::Construct() {
	G4NistManager* nistMan = G4NistManager::Instance();
	G4SDManager* SDMan = G4SDManager::GetSDMpointer();

	// This section defines the materials used, and their properties
	//BEGIN//	
	G4Element* N = new G4Element("Nitrogen", "N", 7, 14.01 * g / mole);
	G4Element* O = new G4Element("Oxygen", "O", 8, 16.00 * g / mole);

	G4Material* air = new G4Material("air", 1.204 * kg / m3, 2);
	air->AddElement(N, 80 * perCent);
	air->AddElement(O, 20 * perCent);
	G4MaterialPropertiesTable* airMPT = new G4MaterialPropertiesTable();
	double airEnergy[2] = { 3.10 * eV, 4.13 * eV };
	double airRIndex[2] = { 1.0003, 1.0003 };
	double airAbsorp[2] = { 1000 * m, 1000 * m };
	airMPT->AddProperty("RINDEX", airEnergy, airRIndex, 2);
	airMPT->AddProperty("ABSLENGTH", airEnergy, airAbsorp, 2);
	air->SetMaterialPropertiesTable(airMPT);

	G4Material* vacuum = new G4Material("vacuum", 15 * g / 6.022 / pow(10, 23) / cm3, 2);
	vacuum->AddElement(N, 80 * perCent);
	vacuum->AddElement(O, 20 * perCent);
	G4MaterialPropertiesTable* vacuumMPT = new G4MaterialPropertiesTable();
	double vacuumEnergy[2] = { 3.10 * eV, 4.13 * eV };
	double vacuumRIndex[2] = { 1., 1. };
	double vacuumAbsorp[2] = { 1000000 * m, 1000000 * m };
	vacuumMPT->AddProperty("RINDEX", vacuumEnergy, vacuumRIndex, 2);
	vacuumMPT->AddProperty("ABSLENGTH", vacuumEnergy, vacuumAbsorp, 2);
	vacuum->SetMaterialPropertiesTable(vacuumMPT);

	G4Element* Si = new G4Element("Silicon", "Si", 14, 28.086 * g / mole);
	//	G4Element* C = new G4Element("Carbon", "C", 6, 12.011 * g / mole);
	G4Element* H = new G4Element("Hydrogen", "H", 1, 1.008 * g / mole);

	G4Material* SiO2 = new G4Material("Silicon Dioxide", 2.648 * g / cm3, 2);
	SiO2->AddElement(Si, 1. / 3.);
	SiO2->AddElement(O, 2. / 3.);

	G4Material* H2O = new G4Material("Water", 1 * g / cm3, 2);
	H2O->AddElement(H, 2. / 3.);
	H2O->AddElement(O, 1. / 3.);

	G4Material* aerogel = new G4Material("Aerogel", 0.11 * g / cm3, 2);
	aerogel->AddMaterial(SiO2, 4.2 * perCent);//62.5 * perCent);
	aerogel->AddMaterial(air, 95.8 * perCent);//37.4 * perCent);
	//	aerogel->AddElement(C, .1 * perCent);
	G4MaterialPropertiesTable* aerogelMPT = new G4MaterialPropertiesTable();
	double aerogelEnergy[2] = { 3.10 * eV, 4.13 * eV };
	double aerogelRIndex[2] = { 1.03, 1.03 };
	double aerogelAbsorp[2] = { 0.15 * m, 0.15 * m };
	aerogelMPT->AddProperty("RINDEX", aerogelEnergy, aerogelRIndex, 2);
	aerogelMPT->AddProperty("ABSLENGTH", aerogelEnergy, aerogelAbsorp, 2);
	aerogel->SetMaterialPropertiesTable(aerogelMPT);

	G4Material* Al = nistMan->FindOrBuildMaterial("G4_Al");

	G4Material* glass = nistMan->FindOrBuildMaterial("G4_Pyrex_Glass");
	G4MaterialPropertiesTable* glassMPT = new G4MaterialPropertiesTable();
	double glassEnergy[2] = { 3.10 * eV, 4.13 * eV };
	double glassRIndex[2] = { 1.5, 1.5 };
	double glassAbsorp[2] = { 5 * m, 5 * m };
	glassMPT->AddProperty("RINDEX", glassEnergy, glassRIndex, 2);
	glassMPT->AddProperty("ABSLENGTH", glassEnergy, glassAbsorp, 2);
	glass->SetMaterialPropertiesTable(glassMPT);
	//END//


	// This section creates the main volume to simulate the outside environment for the detector.
	//BEGIN//
	G4Box* worldSolid = new G4Box("world", 1.5 * m, 1.5 * m, 1.5 * m);
	G4LogicalVolume* worldLog = new G4LogicalVolume(worldSolid, vacuum, "worldLogical");
	G4VPhysicalVolume* worldPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, 0), worldLog, "worldPhysical", 0, false, 0);
	//END//


	// Define upper air space height and width, aerogel space height, and box thickness
	double lowerSpaceH = 16.1 * cm;
	double lowerSpaceW = 11.4 * cm;
	double lowerSpaceL = 25.3 * cm;
	double aerogelSpaceH = 11.2 * cm;
	double aerogelSpaceW = 11.2 * cm;
	double aerogelSpaceL = 11.2 * cm;
	double mainBoxThickness = 1.5 * mm;

	// This section creates a main box of aluminum to place regions of detector inside.
	// The main box carries surface properties of interior coating.
	//BEGIN//
	G4Box* mainBoxSolid = new G4Box("mainBoxSolid", 1.5 * m, 1.5 * m, (lowerSpaceH + aerogelSpaceH) / 2
			+ mainBoxThickness);
	G4LogicalVolume* mainBoxLog = new G4LogicalVolume(mainBoxSolid, Al, "mainBoxLogical");
	G4VPhysicalVolume* mainBoxPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, 0), mainBoxLog, "mainBoxPhysical",
			worldLog, false, 0);
	(void) mainBoxPhys;
	//END//


	// This section creates the regions of air inside the detector, in which
	// the aerogel sits and the PMTs view
	//BEGIN//
	G4Box* lowerSpaceSolid = new G4Box("lowerSpaceSolid", lowerSpaceW / 2, lowerSpaceL / 2, lowerSpaceH / 2);
	G4LogicalVolume* lowerSpaceLog = new G4LogicalVolume(lowerSpaceSolid, air, "lowerSpaceLogical");
	G4VPhysicalVolume* lowerSpacePhys = new G4PVPlacement(0, G4ThreeVector(0, 0, -aerogelSpaceH / 2), lowerSpaceLog,
			"lowerSpacePhysical", mainBoxLog, false, 0);
	(void) lowerSpacePhys;

	G4Box* aerogelSpaceSolid = new G4Box("aerogelSpaceSolid", aerogelSpaceW / 2, aerogelSpaceL / 2, aerogelSpaceH / 2);
	G4LogicalVolume* aerogelSpaceLog = new G4LogicalVolume(aerogelSpaceSolid, air, "aerogelSpaceLogical");
	G4VPhysicalVolume* aerogelSpacePhys = new G4PVPlacement(0, G4ThreeVector(0, 0, lowerSpaceH / 2), aerogelSpaceLog,
			"aerogelSpacePhysical", mainBoxLog, false, 0);
	(void) aerogelSpacePhys;
	//END//


	// Define height of aerogel material, and vertical distance from bottom of aerogel space to bottom of aerogel
	double aerogelH = 5.6 * cm;
	double aerogelW = 11.2 * cm;
	double aerogelL = 11.2 * cm;
	double aerogelZ = 0 * cm;

	// This section creates the block of aerogel.
	//BEGIN//
	G4Box* aerogelSolid = new G4Box("aerogelSolid", aerogelW / 2, aerogelL / 2, aerogelH / 2);
	G4LogicalVolume* aerogelLog = new G4LogicalVolume(aerogelSolid, aerogel, "aerogelLogical");
	G4VPhysicalVolume* aerogelPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, -aerogelSpaceH / 2 + aerogelH / 2
			+ aerogelZ), aerogelLog, "aerogelPhysical", aerogelSpaceLog, false, 0);
	(void) aerogelPhys;
	//END//


	// Define PMT length and diameter
	double PMTDiam = 13 * cm;
	double windowGlassThickness = 1 * mm;
	double PMTLength1 = windowGlassThickness + 0.1 * mm;
	double PMTLength2 = 10 * cm + PMTLength1;

	// This section creates main PMTs to the main aluminum box.  The PMTs consist of a glass tube with
	// a simulated photocathode inside.
	//BEGIN//
	G4RotationMatrix* leftRotation = new G4RotationMatrix(90 * deg, 90 * deg, 0);
	G4RotationMatrix* rightRotation = new G4RotationMatrix(-90 * deg, 90 * deg, 0);

	G4Tubs* PMTLightGuideSolid = new G4Tubs("PMTLightGuideSolid", 0, PMTDiam / 2 + 0.1 * mm, PMTLength2 / 2, 0, 360
			* deg);
	G4LogicalVolume* leftPMTLightGuideLog = new G4LogicalVolume(PMTLightGuideSolid, Al, "leftPMTLightGuideLogical");
	G4VPhysicalVolume* leftPMTLightGuidePhys = new G4PVPlacement(leftRotation, G4ThreeVector(
			(-PMTLength2 - lowerSpaceW) / 2, 0, -aerogelSpaceH / 2), leftPMTLightGuideLog, "leftPMTLightGuidePhysical",
			mainBoxLog, false, 0);
	(void) leftPMTLightGuidePhys;

	G4LogicalVolume* rightPMTLightGuideLog = new G4LogicalVolume(PMTLightGuideSolid, Al, "rightPMTLightGuideLogical");
	G4VPhysicalVolume* rightPMTLightGuidePhys = new G4PVPlacement(rightRotation, G4ThreeVector((PMTLength2
			+ lowerSpaceW) / 2, 0, -aerogelSpaceH / 2), rightPMTLightGuideLog, "rightPMTLightGuidePhysical",
			mainBoxLog, false, 0);
	(void) rightPMTLightGuidePhys;

	G4Tubs* PMTSolid = new G4Tubs("PMTSolid", 0, PMTDiam / 2, PMTLength2 / 2, 0, 360 * deg);
	G4LogicalVolume* leftPMTLog = new G4LogicalVolume(PMTSolid, air, "leftPMTLogical");
	G4VPhysicalVolume* leftPMTPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, 0), leftPMTLog, "leftPMTPhysical",
			leftPMTLightGuideLog, false, 0);
	(void) leftPMTPhys;

	G4LogicalVolume* rightPMTLog = new G4LogicalVolume(PMTSolid, air, "rightPMTLogical");
	G4VPhysicalVolume* rightPMTPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, 0), rightPMTLog, "rightPMTPhysical",
			rightPMTLightGuideLog, false, 0);
	(void) rightPMTPhys;

	G4Tubs* PMTGlassSolid = new G4Tubs("PMTGlassSolid", 0, PMTDiam / 2, PMTLength1 / 2, 0, 360 * deg);
	G4VSensitiveDetector* leftDetector = new PMTDetector("main/left");
	SDMan->AddNewDetector(leftDetector);
	G4LogicalVolume* leftPMTGlassLog =
			new G4LogicalVolume(PMTGlassSolid, glass, "leftPMTGlassLogical", 0, leftDetector);
	G4VPhysicalVolume* leftPMTGlassPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, PMTLength1 / 2 - PMTLength2 / 2),
			leftPMTGlassLog, "leftPMTGlassPhysical", leftPMTLog, false, 0);
	(void) leftPMTGlassPhys;

	G4VSensitiveDetector* rightDetector = new PMTDetector("main/right");
	G4LogicalVolume* rightPMTGlassLog = new G4LogicalVolume(PMTGlassSolid, glass, "rightPMTLogical", 0, rightDetector);
	SDMan->AddNewDetector(rightDetector);
	G4VPhysicalVolume* rightPMTGlassPhys = new G4PVPlacement(0, G4ThreeVector(0, 0, PMTLength1 / 2 - PMTLength2 / 2),
			rightPMTGlassLog, "rightPMTPhysical", rightPMTLog, false, 0);
	(void) rightPMTGlassPhys;

	G4Tubs* PMTPhotocathodeSolid = new G4Tubs("PMTPhotocathodeSolid", 0, PMTDiam / 2, .05 * mm, 0, 360 * deg);
	G4LogicalVolume* leftPMTPhotocathodeLog = new G4LogicalVolume(PMTPhotocathodeSolid, Al,
			"leftPMTPhotocathodeLogical");
	G4VPhysicalVolume* leftPMTPhotocathodePhys = new G4PVPlacement(0, G4ThreeVector(0, 0, PMTLength1 / 2
			- windowGlassThickness - .05 * mm), leftPMTPhotocathodeLog, "leftPhotocathodePhysical", leftPMTGlassLog,
			false, 0);
	(void) leftPMTPhotocathodePhys;

	G4LogicalVolume* rightPMTPhotocathodeLog = new G4LogicalVolume(PMTPhotocathodeSolid, Al,
			"rightPMTPhotocathodeLogical");
	G4VPhysicalVolume* rightPMTPhotocathodePhys = new G4PVPlacement(0, G4ThreeVector(0, 0, PMTLength1 / 2
			- windowGlassThickness - .05 * mm), rightPMTPhotocathodeLog, "rightPhotocathodePhysical", rightPMTGlassLog,
			false, 0);
	(void) rightPMTPhotocathodePhys;
	//END//


	// This section creates all required optical surfaces
	//BEGIN//
	G4OpticalSurface* aerogelAirOptSurface = new G4OpticalSurface("aerogelAirOpticalSurface", glisur, polished,
			dielectric_dielectric);
	G4LogicalBorderSurface* aerogelAerogelSpaceSurface = new G4LogicalBorderSurface("aerogelAerogelSpaceSurface",
			aerogelPhys, aerogelSpacePhys, aerogelAirOptSurface);
	(void) aerogelAerogelSpaceSurface;
	G4LogicalBorderSurface* aerogelUpperSpaceSurface = new G4LogicalBorderSurface("aerogelUpperSpaceSurface",
			aerogelPhys, lowerSpacePhys, aerogelAirOptSurface);
	(void) aerogelUpperSpaceSurface;

	G4OpticalSurface* glassAirOptSurface = new G4OpticalSurface("glassAirOpticalSurface", glisur, polished,
			dielectric_dielectric);
	G4LogicalBorderSurface* leftMainPMTAirSurface = new G4LogicalBorderSurface("leftMainPMTAirSurface",
			leftPMTGlassPhys, leftPMTPhys, glassAirOptSurface);
	(void) leftMainPMTAirSurface;
	G4LogicalBorderSurface* rightMainPMTAirSurface = new G4LogicalBorderSurface("rightMainPMTAirSurface",
			rightPMTGlassPhys, leftPMTPhys, glassAirOptSurface);
	(void) rightMainPMTAirSurface;

	// LIGHT BOX SURF
	G4OpticalSurface* mainBoxOptSurface = new G4OpticalSurface("mainBoxOpticalSurface", glisur, ground,
			dielectric_metal);
	double mainBoxEnergy[2] = { 3.10 * eV, 4.13 * eV };
	double mainBoxRefl[2] = { 0.97, 0.97 };
	G4MaterialPropertiesTable* mainBoxSurfaceMPT = new G4MaterialPropertiesTable();
	mainBoxSurfaceMPT->AddProperty("REFLECTIVITY", mainBoxEnergy, mainBoxRefl, 2);
	mainBoxOptSurface->SetMaterialPropertiesTable(mainBoxSurfaceMPT);
	mainBoxOptSurface->SetPolish(0.0);
	G4LogicalSkinSurface* mainBoxSurface = new G4LogicalSkinSurface("mainBoxSurface", mainBoxLog, mainBoxOptSurface);
	(void) mainBoxSurface;

	// Light guide surface
	G4OpticalSurface* lightGuideOptSurface = new G4OpticalSurface("lightGuideOpticalSurface", glisur, polished,
			dielectric_metal);
	G4LogicalSkinSurface* leftLightGuideSurface = new G4LogicalSkinSurface("leftLightGuideSurface",
			leftPMTLightGuideLog, lightGuideOptSurface);
	(void) leftLightGuideSurface;
	G4LogicalSkinSurface* rightLightGuideSurface = new G4LogicalSkinSurface("rightLightGuideSurface",
			rightPMTLightGuideLog, lightGuideOptSurface);
	(void) rightLightGuideSurface;

	G4OpticalSurface* photocathodeOptSurface = new G4OpticalSurface("photocathodeOpticalSurface", glisur, polished,
			dielectric_metal);
	double PCEnergy[2] = { 3.10 * eV, 4.13 * eV };
	double PCRefl[2] = { 0.0, 0.0 };
	double PCEff[2] = { 0.07, 0.07 };
	G4MaterialPropertiesTable* PCMPT = new G4MaterialPropertiesTable();
	PCMPT->AddProperty("REFLECTIVITY", PCEnergy, PCRefl, 2);
	PCMPT->AddProperty("EFFICIENCY", PCEnergy, PCEff, 2);
	photocathodeOptSurface->SetMaterialPropertiesTable(PCMPT);
	G4LogicalSkinSurface* leftMainPMTPhotocathodeSurface = new G4LogicalSkinSurface("leftMainPMTPhotocathodeSurface",
			leftPMTPhotocathodeLog, photocathodeOptSurface);
	(void) leftMainPMTPhotocathodeSurface;
	G4LogicalSkinSurface* rightMainPMTPhotocathodeSurface = new G4LogicalSkinSurface("rightMainPMTPhotocathodeSurface",
			rightPMTPhotocathodeLog, photocathodeOptSurface);
	(void) rightMainPMTPhotocathodeSurface;
	//END//


	return worldPhys;
}
